class Hora < ActiveRecord::Base
  MAusencia=0
  MHorasInsuficientes=1
  MHorasExtras=2
  MHorasCompensadas=3
  TDescuentos=[MAusencia,MHorasInsuficientes]
  belongs_to :usuario
  belongs_to :hora, :foreign_key=>:compensada_id,:dependent=>:destroy
  belongs_to :proceso,:class_name=>'HoraProceso'
  has_one :hora_compensada, :class_name=>"Hora", :foreign_key=>:compensada_id,:dependent=>:destroy
  
  named_scope :por_proceso_id, lambda{|id| {:conditions=>["proceso_id=#{id}"]}}
  named_scope :por_usuario_id, lambda{|id| {:conditions=>["usuario_id=#{id}"]}}
  named_scope :por_periodo, lambda{|i,f| {:conditions=>["fecha BETWEEN ? AND ?",i,f]}}
  named_scope :por_motivo, lambda{|m| {:conditions=>["motivo = #{m}"]}}

  TIPOS={false=>"Negativas.",true=>"Positivas."}
  
  #NO CAMBIAR EL ORDEN: se utiliza en el codigo al crear las horas la siguiente condicion,
  #(motivo_index >) 1 ? 1 : 0 es decir si a la hora a crear se le asigno un motivo con indice mayor a uno
  #la hora creada es una hora de tipo entrada o positiva, caso contrario; es una hora negativa
  MOTIVOS=["Ausencia.","Horas trabajadas insuficiente." ,"Horas extras trabajadas.", "Horas Compensadas."]

  def self.get_tipo(motivo)
    return nil if motivo==3
    (motivo >  1) ? 1 : 0
  end

  def tipo_texto
    TIPOS[self.tipo]
  end

  def self.tiposjq
    data=[]
    data<<"'':Ambos"
    TIPOS.values.each_with_index { |t,i|data<<"#{i}:#{t}"  }
    data.join(";")
  end

  def motivo_texto
    MOTIVOS[self.motivo]
  end

  def self.motivosjq
    data=[]
    data<<"'':Todos"
    MOTIVOS.each_with_index { |m,i|data<<"#{i}:#{m}"  }
    data.join(";")
  end

  
  
  #  def self.generar_horas(month,year)
  #    h=Hora.last
  #    proceso_id = h ? h.proceso_id+1 : 1
  #    periodo=Date.new(month,year)
  #    usuarios=Usuario.activos.all
  #    periodo.beginning_of_month.upto(periodo.end_of_month) do |date|
  #      usuarios.each do |usuario|
  #        dm=DiaMarcacion.new(usuario,date)
  #        self.generar(dm,proceso_id)
  #      end
  #    end
  #    proceso_id
  #  end
  
  
  
  
  
  
  
  
  
  
  
  
  
  #Se encarga de generar las #Horas a partir de las marcaciones incluidas en el
  #objeto #DiaMarcacion que recibe como parametro, si este objeto no posee
  #horarios se retorna ya que de esto dependen los calculos.
  #El método retorna -1,0 o 1, dependiendo si no se generaron horas, se generaron
  #horas negativas o se generaron horas positivas
  #<b>OBS:</b>Para su correcto funcionamiento es necesario que las marcaciones esten
  #arregladas, y que los parametros necesarios esten configurados.
  def self.generar(dia_marcacion,proceso_id)
    horas_generadas=[]
    if dia_marcacion.autorizacion
      logger.info "\n\n=============================================================="
      logger.info "dia_marcacion.fecha => #{dia_marcacion.fecha}"    
      logger.info "horarios => #{dia_marcacion.horarios.inspect}"
      logger.info "==============================================================\n\n"
    end
    #return horas_generadas if !dia_marcacion.marcaciones.empty? and (dia_marcacion.horarios.empty? or !dia_marcacion.autorizacion.nil?)

    tolerancia_llegada_tardia= dia_marcacion.marcaciones.empty? ? 0 : Parametro.find_by_codigo('PV01').valor.to_f
    minimo_horas_extras=Parametro.find_by_codigo('PV02').valor.to_f
    minutos_trabajados=dia_marcacion.horas_trabajadas_en_minutos
    carga_horaria_en_minutos=dia_marcacion.carga_horaria_del_dia_en_minutos
    cantidad_para_negativas=minutos_trabajados+tolerancia_llegada_tardia
    cantidad_para_positivas=minutos_trabajados-minimo_horas_extras
    horas_extras=0.0
    horas_trabajadas=0.0
    if dia_marcacion.autorizacion
      logger.info "\n\n=============================================================="
      logger.info "dia_marcacion.autorizacion => #{dia_marcacion.autorizacion.try(:fecha)}"
      logger.info "==============================================================\n\n"
    end
    if (cantidad_para_positivas > carga_horaria_en_minutos) and dia_marcacion.autorizacion
      hora,horas_extras,horas_trabajadas=self.generar_horas_positivas(dia_marcacion,proceso_id)
      if dia_marcacion.autorizacion
        logger.info "\n\n=============================================================="
        logger.info "positivas => "
        logger.info "==============================================================\n\n"
      end
      horas_generadas<<hora
      cantidad_para_negativas=(horas_trabajadas*60)+tolerancia_llegada_tardia
    end
    if cantidad_para_negativas < carga_horaria_en_minutos
      if dia_marcacion.autorizacion
        logger.info "\n\n=============================================================="
        logger.info "negativas => "
        logger.info "==============================================================\n\n"
      end
      hora=self.generar_horas_negativas(dia_marcacion,carga_horaria_en_minutos,cantidad_para_negativas,proceso_id)
      horas_generadas<<hora
    end
    horas_generadas
  end

  #Se encarga de compensar el saldo de horas, es decir, tomando como referencia
  #el objeto hora que recibe como parametro, crea otros dos objetos hora con el
  #mismo valor que el que recibio de parametro, pero uno con la misma fecha pero
  #de tipo opuesto y otro del mismo tipo con con fecha un mes adelante.
  #los relaciona de modo que si uno se elimina se elimina el otro.
  #retorna un array con ambos objetos.
  def self.compensar(hora)
    valor=self.resumen(hora.usuario,hora.fecha,:valor).last.last.to_f
    cantidad=self.resumen(hora.usuario,hora.fecha,:cantidad).last.last.to_f
    hora_mismo_mes=Hora.new
    hora_mismo_mes.tipo=valor>0 ? false : true
    hora_mismo_mes.cantidad=cantidad.abs
    hora_mismo_mes.valor=valor.abs
    hora_mismo_mes.fecha=hora.fecha.end_of_month
    hora_mismo_mes.usuario_id=hora.usuario_id
    hora_mismo_mes.motivo=3#Motivo:horas compensadas

    hora_sgte_mes=Hora.new
    hora_sgte_mes.tipo=!hora_mismo_mes.tipo
    hora_sgte_mes.cantidad=hora_mismo_mes.cantidad
    hora_sgte_mes.valor=hora_mismo_mes.valor
    hora_sgte_mes.fecha=hora_mismo_mes.fecha.advance(:months=>1).beginning_of_month
    hora_sgte_mes.usuario_id=hora_mismo_mes.usuario_id
    hora_sgte_mes.motivo=3#Motivo:horas compensadas
    hora_sgte_mes.save

    hora_mismo_mes.hora_compensada=hora_sgte_mes
    hora_mismo_mes.save
    [hora_mismo_mes,hora_sgte_mes]
  end

  #Genera un resumen de las horas que pertenezcan al usuario, para el mes de la 
  #fecha de inicio, el resumen muestra su valor en horas o valor.
  #Retorna un vector que contine el objeto hora, el valor positivo, el valor
  #negativo,y el saldo
  def self.resumen(usuario,inicio,campo=:cantidad)
    inicio=inicio.beginning_of_month
    horas=self.por_usuario_id(usuario.id).por_periodo(inicio,inicio.end_of_month)
    resumen=[]
    saldo=negativas=positivas=0
    horas.each do |hora|
      hora.tipo ? positivas=hora.send(campo.to_sym) : negativas=hora.send(campo.to_sym)
      saldo+=(positivas-negativas)
      resumen<<[hora,positivas,negativas,saldo]
      positivas=0
      negativas=0
    end
    resumen
  end

  #Crear objetos #Movimiento a partir de los objetos #Hora, esto movimientos serán
  #considerados en la generación del recibo.
  #<b>OBS:</b>Para que funcione correctamente deben estar cargados los parametros necesarios
  #Tipo movimientos para descuentos y para acreditaciones
  def self.liquidar(inicio,proceso_id)
    id_descuentos=Parametro.find_by_codigo('PTMOV02').try(:valor)
    id_acreditaciones=Parametro.find_by_codigo('PTMOV01').try(:valor)
    return false if (id_descuentos.to_i<=0 or id_acreditaciones.to_i<=0)
    inicio=inicio.beginning_of_month
    funcionarios=self.por_periodo(inicio,inicio.end_of_month).all(:select=>'distinct(usuario_id)')
    cant=0
    movimientos=[]
    funcionarios.each do |f|
      resumen=self.resumen(f.usuario,inicio,:valor)
      valor= resumen.empty? ? 0 : resumen.last.last.to_f
      
      if valor !=0
        mov_id= valor<0 ? id_descuentos : id_acreditaciones
        movimientos<<self.crear_movimiento(f.usuario_id,valor,mov_id,proceso_id)
        cant+=1
      end
    end
    movimientos
  end

  private
  #Recibe como parametro un objeto #DiaMarcacion el cual utiliza para determinar
  #cuantas horas se trabajo en <b>horario laboral normal</b>, y cuantas en <b>horas extras</b>
  #una vez que tiene determinada la cantidad de horas extras, calcula el valor
  #monetario de dichas horas, luego crea un nuevo objeto #Hora y retrona un array con la <b>cantidad en minutos</b>
  #de horas trabajadas en <b>horario laboral normal</b>, y cuantas en <b>horas extras</b>
  #retorna tambien la cantidad de horas trabajadas en su horario normal para luego,
  #con ese valor, calcular si trabajo menos que las horas exigidas por su carga laboral.
  def self.generar_horas_positivas(dia_marcacion,proceso_id)
    horas_extras=0.0
    horas_trabajadas=0.0
    horas_extras,horas_trabajadas=determinar_horas_extras_y_trabajadas(dia_marcacion)
    valor=calcular_valor(dia_marcacion,horas_extras)
    hora=self.crear(dia_marcacion,MHorasExtras,horas_extras,valor,proceso_id)
    [hora,horas_extras,horas_trabajadas]#en minutos
  end

  #TODO: Verificar el calculo del valor de <b>horas_extras</b>

  #Determina cuantas horas se trabajo en horario normal y cuantas en horas extras
  #trae las franjas de horas extras a la que corresponde su autorizacion,
  #determina cual de sus marcaciones corresponde a esas franjas y calcula la
  #cantidad de horas extras teniendo en cuenta cuanto el porcentaje extra que se
  #paga en esa franja, luego calcula la cantidad de horas trabajadas.
  def self.determinar_horas_extras_y_trabajadas(dia_marcacion)
    franjas=Franja.por_autorizacion(dia_marcacion.autorizacion)
    marcaciones_extras=get_marcaciones_extras(dia_marcacion.marcaciones,dia_marcacion.autorizacion)
    marcaciones_por_franjas=get_marcaciones_por_franja(marcaciones_extras,franjas)
    marcaciones=dia_marcacion.marcaciones.clone
    marcaciones_extras.each{|m| marcaciones.delete(m) }

    horas_extras=0.0
    cantidad_horas_extras_anterior=0.0
    exit=false
    marcaciones_por_franjas.each_key do |franja|
      cantidad_horas_extras=((marcaciones_por_franjas[franja][1]-marcaciones_por_franjas[franja][0]).abs/3600)
      total_horas_extras_trabajadas=cantidad_horas_extras+cantidad_horas_extras_anterior
      if (total_horas_extras_trabajadas>= dia_marcacion.carga_horaria_extra)
        cantidad_horas_extras=dia_marcacion.carga_horaria_extra-cantidad_horas_extras_anterior
        exit=true
      end
      horas_extras=horas_extras+cantidad_horas_extras+cantidad_horas_extras*(franja.porcentaje.to_f/100)
      break if exit
      cantidad_horas_extras_anterior=cantidad_horas_extras
    end

    horas_trabajadas=0.0
    (0..(marcaciones.length-1)).step(2) {|i| (horas_trabajadas+=(marcaciones[i+1].hora-marcaciones[i].hora)/3600)} unless marcaciones.count%2!=0
    horas_trabajadas=sprintf("%.3f",horas_trabajadas).to_f
    
    #patch_if_marcaciones_incompletas(horas_extras,horas_trabajadas)
    
    [horas_extras,horas_trabajadas]
  end

  #Crea un nuevo objeto #Hora de tipo negativa, retorna el objeto creado
  def self.generar_horas_negativas(dia_marcacion,minutos_requeridos,minutos_trabajados,proceso_id)
    return nil if dia_marcacion.feriado?
    motivo  = minutos_trabajados.zero? ? MAusencia : MHorasInsuficientes # ausencia : horas trabajadas insuficinetes
    cantidad= (minutos_requeridos-minutos_trabajados)/60
    valor=calcular_valor(dia_marcacion,cantidad)
    self.crear(dia_marcacion,motivo,cantidad,valor,proceso_id)
  end

  #TODO: verificar la linea de código: sueldo_por_dia=dia_marcacion.usuario.send(APP_CONFIG[:campo_sueldo])/cant_dias
  #el campo de sueldo.. como se determina?

  #Retorna el valor monetario de las horas
  def self.calcular_valor(dia_marcacion,cantidad)
    #cant_dias=(dia_marcacion.fecha.end_of_month.day-dia_marcacion.fecha.beginning_of_month.day)+1
    cant_dias=30 #el gobierno establece que los recibos se calculen siempre por 30 dias
    sueldo_por_dia=dia_marcacion.usuario.send(APP_CONFIG[:campo_sueldo])/cant_dias
    sueldo_por_hora=sueldo_por_dia/dia_marcacion.carga_horaria
    cantidad*sueldo_por_hora
  end
  
  def self.crear_movimiento(usuario_id,valor,tipo_movimiento_id,hora_proceso_id)
    mov=Movimiento.new
    mov.fecha=Date.today
    mov.usuario_id=usuario_id
    mov.valor=valor.abs
    mov.tipo_movimiento_id=tipo_movimiento_id.to_i
    mov.hora_proceso_id=hora_proceso_id.to_i
    mov.save
  end

  #De un conjunto de marcaciones, determina cuales son las marcaciones a considerar
  #como marcaciones extra, retorna un array con la marcacion de entrada y salida
  # que correspnden a la horas extras.
  def self.get_marcaciones_extras(marcaciones,autorizacion)
    salidas=[]
    marcaciones.each_with_index{|n,i| salidas<<n if i%2==1}
    salida_extra=get_salida_extra(salidas,autorizacion.salida)
    entrada_extra=get_entrada_extra(marcaciones,salida_extra)
    [entrada_extra,salida_extra]
  end

  #De un conjunto de marcaciones que son consideradas como marcaciones de salida,
  #determina cual es la marcacion de salida a considerar como marcacion extra
  def self.get_salida_extra(marcaciones_salida,autorizacion_salida)
    distancia=2000000
    salida_extra=nil
    marcaciones_salida.each do |salida|
      if (salida.hora-autorizacion_salida).abs<distancia
        salida_extra=salida
        distancia=(salida.hora-autorizacion_salida).abs
      end
    end
    salida_extra
  end

  #Tomando como referencia una marcacion considereda como la marcacion de salida
  #de las horas extra, retorna la marcacion de entrada que le corresponde
  def self.get_entrada_extra(marcaciones,salida_extra)
    marcaciones[marcaciones.index(salida_extra)-1]
  end

  #Tomando como referencia un par de marcaciones, consideradas como marcaciones
  #de horas extra, y las franjas de horas extras en las que caen esta, crea, de
  #ser necesario, uno o mas pares de marcaciones, cada par en una franja distinta
  #Retorna un hash, en el que el key es una fraja y el dato un array con las
  #marcaciones de entrada y salida para esa franja.
  #<b>OBS:</b> Solo funciona si las marcaciones extras abarcan hasta 3 franjas
  def self.get_marcaciones_por_franja(marcaciones_extra,franjas)
    return {franjas[0] =>[marcaciones_extra[0].hora,marcaciones_extra[1].hora] } if franjas.count==1
    marcaciones_por_franja={}

    franja=get_franja_de_marcacion(franjas,marcaciones_extra[0])
    entrada_para_primera_franja=marcaciones_extra[0].hora
    salida_para_primera_franja=franja.hasta
    marcaciones_por_franja[franja] = [entrada_para_primera_franja,salida_para_primera_franja]

    franjas.delete(franja)
    franja=get_franja_de_marcacion(franjas,marcaciones_extra[1])
    entrada_para_ultima_franja=franja.desde
    salida_para_ultima_franja=marcaciones_extra[1].hora
    marcaciones_por_franja[franja] = [entrada_para_ultima_franja,salida_para_ultima_franja]

    franjas.delete(franja)
    marcaciones_por_franja[franjas[0]] = [salida_para_primera_franja,entrada_para_ultima_franja] if franjas.count==3

    return marcaciones_por_franja
  end

  #Selecciona de entre unas franjas candidatas, a cual le pertenece la marcacion
  #que recibe como parametro y retorna diccha franja releccionada
  def self.get_franja_de_marcacion(franjas,marcacion)
    distancia=200000
    franja=nil
    franjas.each do |f|
      if (f.hasta-marcacion.hora).abs<distancia
        franja=f
        distancia=(f.hasta-marcacion.hora).abs
      end
    end
    franja
  end

  def self.crear(dia_marcacion,motivo,cantidad,valor,proceso_id)
    tipo = TDescuentos.include?(motivo) ? 0 : 1
    self.create(:tipo=>tipo,:fecha=>dia_marcacion.fecha,
      :cantidad=>cantidad,:valor=>valor,
      :usuario=>dia_marcacion.usuario,
      :motivo=>motivo,:proceso_id=>proceso_id)
  end
end